#pragma once


#ifdef _WIN32

#include <WinSock2.h>
#include <WS2tcpip.h>

#include <cstring>
#include <string>
#include <cerrno>
#include <unordered_set>
#include <functional>

#include "../service/rpc_service.h"
#include "../core/serial_util.h"

#pragma comment(lib, "ws2_32.lib")

namespace lurpc {

/*************************
 * Socket Client
 * Windows platform
 ************************/
class socket_client {
public:
    static constexpr int BUF_SIZE = 2048;

public:
    static std::string send_msg(int, const std::string&, const std::string&);

public:
    socket_client();
    socket_client(const socket_client&) = delete;
    socket_client(socket_client&&) noexcept;

public:
    ~socket_client();

public:
    int connect_to_server(int, const std::string&);
    std::string exec_cmd(const std::string&);
    int close_conn();

    template<typename T, typename ... Args>
    T remote_call(uint32_t service_id, const Args& ... _args);

private:
    SOCKET _sock;
};

std::string socket_client::send_msg(int _port, const std::string& _host, const std::string& _msg) {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {
        printf("WSAStartup error: %s\n", strerror(errno));
        WSACleanup();
        return "";
    }

    SOCKET cli_sock = socket(AF_INET, SOCK_STREAM, 0);

    if (cli_sock < 0) {
        printf("socket error: %s\n", strerror(errno));
        WSACleanup();
        return "";
    }

    std::string ans;

    sockaddr_in srv_addr {
            .sin_family = AF_INET,
            .sin_port = htons(_port),
            .sin_addr {
                    .S_un {
                            .S_addr = inet_addr(_host.c_str())
                    }
            },
            .sin_zero = { 0, 0, 0, 0, 0, 0, 0, 0 }
    };

    if (connect(cli_sock, (sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) {
        closesocket(cli_sock);
        printf("connect error: %s\n", strerror(errno));
        WSACleanup();
        return "";
    }

    char buf[BUF_SIZE];

    if (send(cli_sock, _msg.c_str(), _msg.size(), 0) < 0) {
        closesocket(cli_sock);
        printf("write error: %s\n", strerror(errno));
        WSACleanup();
        return "";
    }

    auto r_len = recv(cli_sock, buf, BUF_SIZE, 0);
    if (r_len < 0) {
        closesocket(cli_sock);
        printf("read error: %s\n", strerror(errno));
        WSACleanup();
        return "";
    }

    closesocket(cli_sock);

    ans.append(buf, r_len);

    WSACleanup();
    return ans;

}

socket_client::socket_client() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {
        printf("WSAStartup error: %s\n", strerror(errno));
        WSACleanup();
        _sock = -1;
    }
    else {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
    }
}

socket_client::socket_client(socket_client&& _oth_cli) noexcept {
    _sock = _oth_cli._sock;
    _oth_cli._sock = -1;
}

socket_client::~socket_client() {
    if (_sock >= 0) {
        closesocket(_sock);
    }
    WSACleanup();
}

int socket_client::connect_to_server(int _p, const std::string& _h) {
    if (_sock < 0) {
        printf("socket error: %s\n", strerror(errno));
        return -1;
    }

    sockaddr_in srv_addr{
            .sin_family = AF_INET,
            .sin_port = htons(_p),
            .sin_addr {
                    .S_un {
                            .S_addr = inet_addr(_h.c_str())
                    }
            },
            .sin_zero = { 0, 0, 0, 0, 0, 0, 0, 0 }
    };

    if (connect(_sock, (sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) {
        printf("connect error: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

std::string socket_client::exec_cmd(const std::string& _cmd) {
    char buf[BUF_SIZE];

    if (send(_sock, _cmd.c_str(), _cmd.size(), 0) < 0) {
        printf("write error: %s\n", strerror(errno));
        return "";
    }

    auto r_len = recv(_sock, buf, BUF_SIZE, 0);
    if (r_len < 0) {
        printf("read error: %s\n", strerror(errno));
        return "";
    }

    return std::string(buf, r_len);
}

int socket_client::close_conn() {
    if (_sock >= 0) {
        return closesocket(_sock);
    }
    return 0;
}

template<class T, class... Args>
T socket_client::remote_call(uint32_t service_id, const Args &..._args) {
    service_conf svc = rpc_services::get(service_id);

    // no such service configured in client
    if (svc._port == -1) {
        printf("No such service id in client: %d\n", service_id);
        return {};
    }

    std::string _args_buf = uint32_to_bytes(service_id);
    _args_buf.append(serialize(_args...));

    std::string resp = exec_cmd(_args_buf);

    // no such service registered in server
    if (resp == "[NON_SVC]") {
        printf("No such service id in server: %d\n", service_id);
        return {};
    }

    try {
        return deserialize<T>(resp);
    }
    catch (const std::exception& e) {
        printf("remote call falied: %s\n", e.what());
    }

    return {};
}


/*************************
 * Socket Server
 * Windows platform
 * Using winsock
 ************************/
class socket_server {
public:
    static constexpr int BACKLOG_SIZE = 32;
    static constexpr int KEVENTS_CNT = 64;
    static constexpr int EPEVENTS_CNT = 64;
    static constexpr int BUF_SIZE = 2048;
public:
    socket_server();
    socket_server(const socket_server&) = delete;
    socket_server(socket_server&&) noexcept;
    socket_server(int, std::string, std::function<std::string(const std::string&)>);

public:
    ~socket_server();

public:
    int start();

private:
    void __handle_close(SOCKET, SOCKET);

private:
    SOCKET _sock;
    int _port;
    std::string _host;
    std::unordered_set<SOCKET> _inv_fds;
    std::function<std::string(const std::string&)> _handle_message;

};

socket_server::socket_server() : _sock(INVALID_SOCKET), _port(4399), _host("127.0.0.1"), _handle_message(nullptr) { }

socket_server::socket_server(socket_server&& _sock_srv) noexcept {
    _sock = _sock_srv._sock;
    _port = _sock_srv._port;
    _host = _sock_srv._host;
    _handle_message = _sock_srv._handle_message;
    _sock_srv._sock = -1;
    _sock_srv._port = -1;
    _sock_srv._host.clear();
    _sock_srv._handle_message = nullptr;
}

socket_server::socket_server(int _p, std::string _h, std::function<std::string(const std::string&)> _message_handler)
        : _sock(INVALID_SOCKET), _port(_p), _host(std::move(_h)), _handle_message(std::move(_message_handler)) { }

socket_server::~socket_server() {
    if (_sock >= 0) closesocket(_sock);
    for (auto& fd : _inv_fds) if (fd >= 0) closesocket(fd);
    WSACleanup();
}

int socket_server::start() {
    if (_port < 0) return -1;
    if (_host.empty()) return -1;

    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {
        printf("WSAStartup error: %s\n", strerror(errno));
        return -1;
    }

    if ((_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket error: %s\n", strerror(errno));
        return -1;
    }

    unsigned long ul1 = 1;

    sockaddr_in srv_addr {
            .sin_family = AF_INET,
            .sin_port = htons(_port),
            .sin_addr {
                    .S_un {
                            .S_addr = inet_addr(_host.c_str())
                    }
            },
            .sin_zero = { 0, 0, 0, 0, 0, 0, 0, 0 }
    };

    if (bind(_sock, (sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) {
        printf("bind error: %s\n", strerror(errno));
        return -1;
    }

    if (listen(_sock, BACKLOG_SIZE) < 0) {
        printf("listen error: %s\n", strerror(errno));
        return -1;
    }

    char buf[BUF_SIZE];

    while (true) {
        sockaddr_in cli_addr { };
        int cli_addr_sz = sizeof(cli_addr);
        SOCKET cli_sock = accept(_sock, (sockaddr*) &cli_addr, &cli_addr_sz);
        if (cli_sock == INVALID_SOCKET) {
            printf("accept error: %s\n", strerror(errno));
            continue;
        }
        _inv_fds.insert(cli_sock);
        ioctlsocket(cli_sock, FIONBIO, &ul1);
        char* _r_ptr = buf;
        int r_len = recv(cli_sock, buf, BUF_SIZE, 0);
        if (r_len <= 0) {
            if (r_len < 0) printf("recv error: %s\n", strerror(errno));
            closesocket(cli_sock);
            continue;
        }
        else {
            _r_ptr = buf + r_len;
            while ((r_len = recv(cli_sock, _r_ptr, 2048, 0)) > 0) {
                _r_ptr += r_len;
            }
        }

        std::string _call_bkv = _handle_message(std::string(buf, _r_ptr - buf));

        if (send(cli_sock, _call_bkv.c_str(), _call_bkv.size(), 0) < 0) {
            printf("send error: %s\n", strerror(errno));
            closesocket(cli_sock);
            continue;
        }

    }

    return 0;
}

inline void socket_server::__handle_close(SOCKET _sk, SOCKET _ek) {
    closesocket(_sk);
    _inv_fds.erase(_sk);
    // epoll_ctl(_ek, EPOLL_CTL_DEL, _sk, nullptr);
}

}

#endif
